package com.ywy.bootadmin.elfinder.core.impl.aliyunoss;

import com.aliyun.oss.model.OSSObjectSummary;
import com.ywy.bootadmin.elfinder.ElFinderConstants;
import com.ywy.bootadmin.elfinder.configuration.AliyunOSS;
import com.ywy.bootadmin.elfinder.configuration.ElfinderConfigurationUtils;
import com.ywy.bootadmin.elfinder.core.Target;
import com.ywy.bootadmin.elfinder.core.Volume;
import com.ywy.bootadmin.elfinder.core.VolumeBuilder;
import com.ywy.bootadmin.elfinder.service.VolumeSources;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class AliyunOssFileSystemVolume implements Volume {
    private final String alias;
    private final String source;//存储类型

    private final AliyunOSS aliyunOSSConfig;//阿里云OSS配置
    private final AliyunOssApi aliyunOssApi;//阿里云OSS接口
    private final String bucketName;//bucket名称

    private final Target rootTarget;//根对象

    private AliyunOssFileSystemVolume(Builder builder) {
        this.alias = builder.alias;
        this.source = VolumeSources.ALIYUNOSS.name();
        this.aliyunOSSConfig = builder.config;
        if (aliyunOSSConfig == null) {
            throw new RuntimeException("Please config your aliyun oss");
        }
        this.aliyunOssApi = new AliyunOssApi(aliyunOSSConfig.getEndpoint(), aliyunOSSConfig.getAccessKeyId(), aliyunOSSConfig.getAccessKeySecret());
        this.bucketName = aliyunOSSConfig.getBucketName();
        this.rootTarget = new AliyunOssFileSystemTarget(this, this.aliyunOssApi.getRootOSSObjectSummary(bucketName));

        checkBucket();
    }

    @Override
    public void createFile(Target target) throws IOException {
        this.aliyunOssApi.createFile(this.bucketName, getPath(target));
    }

    @Override
    public void createFolder(Target target) throws IOException {
        String path = getPath(target);
        // 阿里云没有文件夹的概念，模拟的文件夹必须以/结尾，所以单独处理一下
        path = this.aliyunOssApi.fixOssFolderName(path);
        this.aliyunOssApi.createFile(this.bucketName, path);

        // 处理过的文件夹重新设置回去
        OSSObjectSummary ossObjectSummary = this.aliyunOssApi.getOssObjectSummary(bucketName, path);
        ((AliyunOssFileSystemTarget)target).setOssObject(ossObjectSummary);
    }

    @Override
    public void deleteFile(Target target) throws IOException {
        delete(target);
    }

    @Override
    public void deleteFolder(Target target) throws IOException {
        delete(target);
    }

    @Override
    public boolean exists(Target target) {
        return this.aliyunOssApi.exists(this.bucketName, getPath(target));
    }

    @Override
    public Target fromPath(String ossKey) {
        if (ossKey.startsWith(ElFinderConstants.ELFINDER_PARAMETER_FILE_SEPARATOR)) {
            ossKey = ossKey.substring(1);
        }
        if (ossKey.length() == 0) {
            return this.rootTarget;
        }
        OSSObjectSummary ossObjectSummary = this.aliyunOssApi.getOssObjectSummary(bucketName, ossKey);
        AliyunOssFileSystemTarget target = new AliyunOssFileSystemTarget(this, ossObjectSummary);
        return target;
    }

    @Override
    public long getLastModified(Target target) throws IOException {
        return ((AliyunOssFileSystemTarget)target).getLastModified();
    }

    @Override
    public String getMimeType(Target target) throws IOException {
        if (isFolder(target)) {
            return "directory";
        }
        return this.aliyunOssApi.getMimeType(this.bucketName, getPath(target));
    }

    @Override
    public String getAlias() {
        return alias;
    }

    @Override
    public String getName(Target target) {
        try {
            if (isRoot(target)) {
                return "";
            }
            return Paths.get(ElfinderConfigurationUtils.toURI(getPath(target))).getFileName().toString();
        } catch (Exception e) {
            e.printStackTrace();
            return "";
        }
    }

    @Override
    public Target getParent(Target target) {
        String parentPath = getParentPath(target);
        return fromPath(parentPath);
    }

    @Override
    public String getPath(Target target) {
        return ((AliyunOssFileSystemTarget)target).getKey();
    }

    @Override
    public Target getRoot() {
        return this.rootTarget;
    }

    @Override
    public long getSize(Target target) throws IOException {
        return ((AliyunOssFileSystemTarget)target).getSize();
    }

    @Override
    public boolean hasChildFolder(Target target) throws IOException {
        return this.aliyunOssApi.hasChildFolder(this.bucketName, getPath(target));
    }

    @Override
    public boolean isFolder(Target target) {
        return this.aliyunOssApi.isFolder(getPath(target));
    }

    @Override
    public boolean isRoot(Target target) throws IOException {
        return this.aliyunOssApi.isRoot(getPath(target));
    }

    @Override
    public Target[] listChildren(Target target) throws IOException {
        List<OSSObjectSummary> childrenResultList = this.aliyunOssApi.listChildren(this.bucketName, getPath(target));
        List<Target> targets = new ArrayList<>(childrenResultList.size());
        for (OSSObjectSummary ossObjectSummary : childrenResultList) {
            targets.add(fromPath(ossObjectSummary.getKey()));
        }
        return targets.toArray(new Target[targets.size()]);
    }

    @Override
    public InputStream openInputStream(Target target) throws IOException {
        return this.aliyunOssApi.openInputStream(bucketName, getPath(target));
    }

    @Override
    public OutputStream openOutputStream(Target target) throws IOException {
        throw new UnsupportedOperationException();
    }

    @Override
    public void rename(Target origin, Target destination) throws IOException {
        throw new UnsupportedOperationException();
    }

    @Override
    public List<Target> search(String target) throws IOException {
        List<Target> targets = new ArrayList<>(0);
        return Collections.unmodifiableList(targets);
    }

    @Override
    public String getSource() {
        return source;
    }

    @Override
    public void createFile(Target target, InputStream is) throws IOException {
        deleteFile(target);
        this.aliyunOssApi.createFile(bucketName, getPath(target), is);
    }

    @Override
    public void createFile(Target target, String content, String encoding) throws IOException {
        deleteFile(target);
        this.aliyunOssApi.createFile(bucketName, getPath(target), content.getBytes(encoding));
    }

    @Override
    public void createAndCopy(Target src, Target dst) throws IOException {
        if (this.isFolder(src)) {
            this.createAndCopyFolder(src, dst);
        } else {
            this.createAndCopyFile(src, dst);
        }
    }

    // 阿里云OSS需要设置访问权限。点击相应的bucket——版本控制——读写权限——Bucket ACL，设置为公共读或公共读写，否则不能通过链接访问
    @Override
    public String getUrl(Target target) {
        String protocol = "https://";
        if (!StringUtils.isEmpty(aliyunOSSConfig.getBucketDomain())) {
            return protocol + aliyunOSSConfig.getBucketDomain() + ElFinderConstants.ELFINDER_PARAMETER_FILE_SEPARATOR + getPath(target);
        } else {
            return protocol + bucketName + "." + aliyunOSSConfig.getEndpoint() + ElFinderConstants.ELFINDER_PARAMETER_FILE_SEPARATOR + getPath(target);
        }
    }

    @Override
    public String getCsscls(Target target)throws IOException {
        if(isRoot(target)) {
            return "elfinder-navbar-root-onedrive";
        }
        return null;
    }

    @Override
    public String[] getDisabledCmds(Target target) {
        return new String[] {"archive", "rename"};
    }

    /**
     * 创建文件，复制文件内容
     * @param src
     * @param dst
     * @throws IOException
     */
    private void createAndCopyFile(Target src, Target dst) throws IOException {
        InputStream is = this.openInputStream(src);
        createFile(dst, is);
        is.close();
    }

    /**
     * 创建文件，复制文件夹内容
     * @param src
     * @param dst
     * @throws IOException
     */
    private void createAndCopyFolder(Target src, Target dst) throws IOException {
        this.createFolder(dst);

        String dstPath = this.getPath(dst);
        dstPath = this.aliyunOssApi.fixOssFolderName(dstPath);
        for (Target c : this.listChildren(src)) {
            createAndCopy(c, this.fromPath(dstPath + this.getName(c)));
        }
    }

    /**
     * 校验bucket是否存在
     */
    private void checkBucket() {
        if(!this.aliyunOssApi.isBucketExists(bucketName)){
            throw new RuntimeException("Unable to create root dir folder");
        }
    }

    /**
     * 删除文件
     * @param target
     * @throws IOException
     */
    private void delete(Target target) throws IOException {
        if (isFolder(target)) {
            this.aliyunOssApi.deleteFolder(this.bucketName, getPath(target));
        } else {
            this.aliyunOssApi.deleteFile(this.bucketName, getPath(target));
        }
    }

    /**
     * 获取父路径
     * @param target
     * @return
     */
    private String getParentPath(Target target) {
        String parent = Paths.get(ElfinderConfigurationUtils.toURI(getPath(target))).getParent().toString();
        if (parent.equalsIgnoreCase(ElFinderConstants.ELFINDER_PARAMETER_FILE_SEPARATOR)) {
            return "";
        }

        parent = this.aliyunOssApi.fixOssFolderName(parent);

        return parent;
    }

    /**
     * Gets a Builder for creating a new NIO2FileSystemVolume instance.
     *
     * @return a new Builder for NIO2FileSystemVolume.
     */
    public static Builder builder(String alias, AliyunOSS config) {
        return new AliyunOssFileSystemVolume.Builder(alias, config);
    }

    /**
     * Builder NIO2FileSystemVolume Inner Class
     */
    public static class Builder implements VolumeBuilder<AliyunOssFileSystemVolume> {
        // required fields
        private final String alias;
        private final AliyunOSS config;

        public Builder(String alias, AliyunOSS config) {
            this.alias = alias;
            this.config = config;
        }

        @Override
        public AliyunOssFileSystemVolume build() {
            return new AliyunOssFileSystemVolume(this);
        }
    }
}